---
title: Axis - Parallel Brush
---

```jsx live noInline
const data = [
  {
    name: "Adrien",
    strength: 5,
    intelligence: 30,
    speed: 500,
    luck: 3,
  },
  {
    name: "Brice",
    strength: 1,
    intelligence: 13,
    speed: 550,
    luck: 2,
  },
  {
    name: "Casey",
    strength: 4,
    intelligence: 15,
    speed: 80,
    luck: 1,
  },
  {
    name: "Drew",
    strength: 3,
    intelligence: 25,
    speed: 600,
    luck: 5,
  },
  {
    name: "Erin",
    strength: 9,
    intelligence: 50,
    speed: 350,
    luck: 4,
  },
  {
    name: "Francis",
    strength: 2,
    intelligence: 40,
    speed: 200,
    luck: 2,
  },
];
const attributes = [
  "strength",
  "intelligence",
  "speed",
  "luck",
];
const height = 500;
const width = 700;
const padding = {
  top: 100,
  left: 50,
  right: 50,
  bottom: 50,
};

function getMaximumValues() {
  // Find the maximum value for each axis. This will be used to normalize data and re-scale axis ticks
  return attributes.map((attribute) => {
    return data.reduce(
      (memo, datum) => {
        return datum[attribute] > memo
          ? datum[attribute]
          : memo;
      },
      -Infinity,
    );
  });
}

function normalizeData(maximumValues) {
  // construct normalized datasets by dividing the value for each attribute by the maximum value
  return data?.map((datum) => ({
    name: datum.name,
    data: attributes.map(
      (attribute, i) => ({
        x: attribute,
        y:
          datum[attribute] /
          maximumValues[i],
      }),
    ),
  }));
}

function App() {
  const maximumValues =
    getMaximumValues();
  const datasets = normalizeData(
    maximumValues,
  );

  const [state, setState] =
    React.useState({
      maximumValues,
      datasets,
      filters: {},
      activeDatasets: [],
      isFiltered: false,
    });

  function addNewFilters(
    domain,
    props,
  ) {
    const filters = state.filters || {};
    const extent =
      domain &&
      Math.abs(domain[1] - domain[0]);
    const minVal =
      1 / Number.MAX_SAFE_INTEGER;
    filters[props.name] =
      extent <= minVal
        ? undefined
        : domain;
    return filters;
  }

  function getActiveDatasets(filters) {
    // Return the names from all datasets that have values within all filters
    const isActive = (dataset) => {
      return _.keys(filters).reduce(
        (memo, name) => {
          if (
            !memo ||
            !Array.isArray(
              filters[name],
            )
          ) {
            return memo;
          }
          const point = _.find(
            dataset.data,
            (d) => d.x === name,
          );
          return (
            point &&
            Math.max(
              ...filters[name],
            ) >= point.y &&
            Math.min(
              ...filters[name],
            ) <= point.y
          );
        },
        true,
      );
    };

    return state.datasets
      .map((dataset) => {
        return isActive(
          dataset,
          filters,
        )
          ? dataset.name
          : null;
      })
      .filter(Boolean);
  }

  function onDomainChange(
    domain,
    props,
  ) {
    const filters = addNewFilters(
      domain,
      props,
    );
    const isFiltered = !_.isEmpty(
      _.values(filters).filter(Boolean),
    );
    const activeDatasets = isFiltered
      ? getActiveDatasets(filters)
      : state.datasets;

    setState((state) => ({
      ...state,
      activeDatasets,
      filters,
      isFiltered,
    }));
  }

  function isActive(dataset) {
    // Determine whether a given dataset is active
    return !state.isFiltered
      ? true
      : _.includes(
          state.activeDatasets,
          dataset.name,
        );
  }

  function getAxisOffset(index) {
    const step =
      (width -
        padding.left -
        padding.right) /
      (attributes.length - 1);
    return step * index + padding.left;
  }

  return (
    <VictoryChart
      domain={{ y: [0, 1.1] }}
      height={height}
      width={width}
      padding={padding}
      theme={VictoryTheme.clean}
    >
      <VictoryAxis
        style={{
          tickLabels: { fontSize: 20 },
          axis: { stroke: "none" },
        }}
        tickLabelComponent={
          <VictoryLabel
            y={padding.top - 40}
          />
        }
      />
      {state.datasets.map((dataset) => (
        <VictoryLine
          key={dataset.name}
          name={dataset.name}
          data={dataset.data}
          groupComponent={<g />}
          style={{
            data: {
              opacity: isActive(dataset)
                ? 1
                : 0.2,
            },
          }}
        />
      ))}
      {attributes.map(
        (attribute, index) => (
          <VictoryAxis
            dependentAxis
            key={index}
            axisComponent={
              <VictoryBrushLine
                name={attribute}
                width={20}
                onBrushDomainChange={
                  onDomainChange
                }
              />
            }
            offsetX={getAxisOffset(
              index,
            )}
            style={{
              tickLabels: {
                fontSize: 15,
                padding: 15,
                pointerEvents: "none",
              },
            }}
            tickValues={[
              0.2, 0.4, 0.6, 0.8, 1,
            ]}
            tickFormat={(tick) =>
              Math.round(
                tick *
                  state.maximumValues[
                    index
                  ],
              )
            }
          />
        ),
      )}
    </VictoryChart>
  );
}

render(<App />);
```
